/***************************************************************************
 *cr
 *cr            (C) Copyright 1995-2019 The Board of Trustees of the
 *cr                        University of Illinois
 *cr                         All Rights Reserved
 *cr
 ***************************************************************************/

/***************************************************************************
 * RCS INFORMATION:
 *
 *      $RCSfile: GraphicsFltkReps.h,v $
 *      $Author: johns $        $Locker:  $             $State: Exp $
 *      $Revision: 1.138 $       $Date: 2019/01/17 21:20:59 $
 *
 ***************************************************************************
 * DESCRIPTION:
 *  generated by Fast Light User Interface Designer (fluid) version 1.0011
 ***************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "FL/forms.H"
#include "FL/Fl_Counter.H"
#include "FL/Fl_Value_Slider.H"
#include "FL/Fl_Float_Input.H"
#include "FL/Fl_Tabs.H"
#include "AtomRep.h"
#include "Molecule.h"
#include "VMDApp.h"
#include "MoleculeList.h"


/// GUI controls for each draw style, creates rep commands
/// for CmdMolChangeRepItem or CmdMolRep, and sets its valuators 
/// correctly given an AtomRep.
class GraphicsFltkRep : public Fl_Group {
protected:
  GraphicsFltkRep()
  : Fl_Group(10, 380, 295, 235) {}
  virtual void do_reset() {}

  char cmdbuf[256];

public:
  virtual int is_volumetric() { return 0; }
  virtual int is_orbital() { return 0; }
  virtual const char *repcmd() = 0;
  virtual void set_values(AtomRep *) = 0;
  void reset() { do_reset(); }
};


#define CTRWIDTH   120
#define CTRWIDTHSM  80
#define CTRHEIGHT   25

/// counter control used to set radii       
class RadiusCounter : public Fl_Counter {
public:
  RadiusCounter(int x, int y, const char *nm)
  : Fl_Counter(x, y, CTRWIDTH, CTRHEIGHT, nm) {
    minimum(0);
    precision(1);
    step(.1);
    lstep(1);
    align(FL_ALIGN_LEFT);
    when(FL_WHEN_RELEASE);
  }
};


/// counter control used to set grid spacing 
class GridResCounter : public Fl_Counter {
public:
  GridResCounter(int x, int y, const char *nm)
  : Fl_Counter(x, y, CTRWIDTH, CTRHEIGHT, nm) {
    minimum(0.05);
    precision(3);
    step(0.005);
    lstep(0.025);
    align(FL_ALIGN_LEFT);
    when(FL_WHEN_RELEASE);
  }
};


/// counter control used to set fieldline integration delta 
class DeltaCounter : public Fl_Counter {
public:
  DeltaCounter(int x, int y, const char *nm)
  : Fl_Counter(x, y, CTRWIDTH, CTRHEIGHT, nm) {
    minimum(0.05);
    precision(3);
    step(0.005);
    lstep(0.025);
    align(FL_ALIGN_LEFT);
    when(FL_WHEN_RELEASE);
  }
};


/// counter control used to set tessellation resolution
class ResolutionCounter : public Fl_Counter {
public:
  ResolutionCounter(int x, int y, const char *nm)
  : Fl_Counter(x, y, CTRWIDTH, CTRHEIGHT, nm) {
    minimum(1);
    precision(0);
    step(1);
    lstep(5);
    align(FL_ALIGN_LEFT);
    when(FL_WHEN_RELEASE);
  }
};


/// counter control used to set volume extraction step size
class StepCounter : public Fl_Counter {
public:
  StepCounter(int x, int y, const char *nm)
  : Fl_Counter(x, y, 90, CTRHEIGHT, nm) {
    minimum(1);
    precision(0);
    step(1);
    lstep(4);
    align(FL_ALIGN_LEFT);
    when(FL_WHEN_RELEASE);
  }
};


/// chooser control used to select from a list of representation settings
class RepChoice : public Fl_Choice {
public:
  RepChoice(int x, int y, const char *nm)  
  : Fl_Choice(x, y, CTRWIDTH, CTRHEIGHT, nm) {
    color(VMDMENU_CHOOSER_BG);
    selection_color(VMDMENU_CHOOSER_SEL);
    align(FL_ALIGN_LEFT);
  }
  int setvalue(int newvalue) {
    if (newvalue >= 0 && newvalue < size())
      return Fl_Choice::value(newvalue);
    return value();
  }
};

/// narrow chooser control used to select from a list of representation settings
class RepChoiceSmall : public Fl_Choice {
public:
  RepChoiceSmall(int x, int y, const char *nm)  
  : Fl_Choice(x, y, CTRWIDTHSM, CTRHEIGHT, nm) {
    color(VMDMENU_CHOOSER_BG);
    selection_color(VMDMENU_CHOOSER_SEL);
    align(FL_ALIGN_LEFT);
  }
  int setvalue(int newvalue) {
    if (newvalue >= 0 && newvalue < size())
      return Fl_Choice::value(newvalue);
    return value();
  }
};


/// slider control with text entry box, used to select isovalues, etc
class IsoSlider : public Fl_Value_Slider {
public:
  IsoSlider(int x, int y, const char *nm)
  : Fl_Value_Slider(x, y, CTRWIDTH+90, CTRHEIGHT, nm) {
    color(VMDMENU_SLIDER_BG);
    align(FL_ALIGN_LEFT);
    type(FL_HOR_SLIDER);
  }
};

/// shorter slider control for use with longer labels
class ShortSlider : public Fl_Value_Slider {
public:
  ShortSlider(int x, int y, const char *nm)
  : Fl_Value_Slider(x, y, CTRWIDTH+50, CTRHEIGHT, nm) {
    color(VMDMENU_SLIDER_BG);
    align(FL_ALIGN_LEFT);
    type(FL_HOR_SLIDER);
  }
};

/// 'Lines' representation controls
class GraphicsFltkRepLines : public GraphicsFltkRep {
public:
  GraphicsFltkRepLines(Fl_Callback *cb, void *v) {
    thickness = new ResolutionCounter(x()+170, y()+120, "Thickness");
    thickness->callback(cb, v);
    Fl_Group::end();
  }
  const char *repcmd() {
    sprintf(cmdbuf, "Lines %f", thickness->value());
    return cmdbuf;
  }
  void set_values(AtomRep *rep) {
    thickness->value(rep->get_data(AtomRep::LINETHICKNESS));
  }
 
protected:
  void do_reset() {
    thickness->value(1);
  }

private:
  Fl_Counter *thickness;
};


/// 'Bonds' representation controls
class GraphicsFltkRepBonds : public GraphicsFltkRep {
public:
  GraphicsFltkRepBonds(Fl_Callback *cb, void *v) {
    radius = new RadiusCounter(x()+170, y()+90, "Bond Radius");
    resolution= new ResolutionCounter(x()+170,y()+120,"Bond Resolution");
    radius->callback(cb, v);
    resolution->callback(cb, v);
    Fl_Group::end();
  }
  const char *repcmd() {
    sprintf(cmdbuf, "Bonds %f %f", radius->value(), resolution->value());
    return cmdbuf;
  }
  void set_values(AtomRep *rep) {
    radius->value(rep->get_data(AtomRep::BONDRAD));
    resolution->value(rep->get_data(AtomRep::BONDRES));
  }

protected:
  void do_reset() {
    radius->value(0.3);
    resolution->value(12);
  }

protected:
  Fl_Counter *radius;
  Fl_Counter *resolution;
};


/// 'DynamicBonds' representation controls
class GraphicsFltkRepDynamicBonds : public GraphicsFltkRep {
private:
  Fl_Counter *distance;
  Fl_Counter *radius;
  Fl_Counter *resolution;
public:
  GraphicsFltkRepDynamicBonds(Fl_Callback *cb, void *v) {
    distance = new RadiusCounter(x()+170,y()+60,"Distance Cutoff");
    distance->callback(cb, v);
    radius = new RadiusCounter(x()+170, y()+90, "Bond Radius");
    radius->callback(cb, v);
    resolution= new ResolutionCounter(x()+170,y()+120,"Bond Resolution");
    resolution->callback(cb, v);
    Fl_Group::end();
  }
protected:
  void do_reset() {
    distance->value(1.6);
    radius->value(0.3);
    resolution->value(12.0);
  }
public:
  const char *repcmd() {
    sprintf(cmdbuf, "DynamicBonds %f %f %f", distance->value(), radius->value(),
      resolution->value());
    return cmdbuf;
  }
  void set_values(AtomRep *rep) {
    distance->value(rep->get_data(AtomRep::SPHERERAD));
    radius->value(rep->get_data(AtomRep::BONDRAD));
    resolution->value(rep->get_data(AtomRep::BONDRES));
  }
};

 
/// 'CPK' representation controls
class GraphicsFltkRepCPK : public GraphicsFltkRep {
public:
  GraphicsFltkRepCPK(Fl_Callback *cb, void *v) {
    bradius = new RadiusCounter(x()+170,y()+90,"Bond Radius");
    bresolution= new ResolutionCounter(x()+170,y()+120,"Bond Resolution");
    bradius->callback(cb, v);
    bresolution->callback(cb, v);

    sradius = new RadiusCounter(x()+170,y()+30,"Sphere Scale");
    sresolution= new ResolutionCounter(x()+170,y()+60,"Sphere Resolution");
    sradius->callback(cb, v);
    sresolution->callback(cb, v);

    Fl_Group::end();
  }

  const char *repcmd() {
    sprintf(cmdbuf, "CPK %f %f %f %f", sradius->value(), bradius->value(),
            sresolution->value(), bresolution->value());
    return cmdbuf;
  }
  void set_values(AtomRep *rep) {
    bradius->value(rep->get_data(AtomRep::BONDRAD));
    bresolution->value(rep->get_data(AtomRep::BONDRES));
    sradius->value(rep->get_data(AtomRep::SPHERERAD));
    sresolution->value(rep->get_data(AtomRep::SPHERERES));
  }

protected:
  void do_reset() {
    bradius->value(0.3);
    bresolution->value(12);
    sradius->value(1.0);
    sresolution->value(12);
  }

private:
  Fl_Counter *bradius;
  Fl_Counter *bresolution;
  Fl_Counter *sradius;
  Fl_Counter *sresolution;
};


/// 'Points' representation controls
class GraphicsFltkRepPoints : public GraphicsFltkRep {
public:
  GraphicsFltkRepPoints(Fl_Callback *cb, void *v) {
    size = new ResolutionCounter(x()+170, y()+120, "Size");
    size->callback(cb, v);
    Fl_Group::end();
  }
  const char *repcmd() {
    sprintf(cmdbuf, "Points %f", size->value());
    return cmdbuf;
  }
  void set_values(AtomRep *rep) {
    size->value(rep->get_data(AtomRep::LINETHICKNESS));
  }

protected:
  void do_reset() {
    size->value(1);
  }

private:
  Fl_Counter *size;
};


#if defined(VMDLATTICECUBES)
/// 'LatticeCubes' representation controls
class GraphicsFltkRepLatticeCubes : public GraphicsFltkRep {
public:
  GraphicsFltkRepLatticeCubes(Fl_Callback *cb, void *v) {
    radius = new RadiusCounter(x()+170,y()+90,"Sphere Scale");
    radius->callback(cb, v);
    Fl_Group::end();
  }

  const char *repcmd() {
    sprintf(cmdbuf, "LatticeCubes %f", radius->value());
    return cmdbuf;
  }
  void set_values(AtomRep *rep) {
    radius->value(rep->get_data(AtomRep::SPHERERAD));
  }

protected:
  void do_reset() {
    radius->value(1.0);
  }

protected:
  Fl_Counter *radius;
};
#endif


/// 'VDW' representation controls
class GraphicsFltkRepVDW : public GraphicsFltkRep {
public:
  GraphicsFltkRepVDW(Fl_Callback *cb, void *v) {
    radius = new RadiusCounter(x()+170,y()+90,"Sphere Scale");
    resolution= new ResolutionCounter(x()+170,y()+120,"Sphere Resolution");
    radius->callback(cb, v);
    resolution->callback(cb, v);
    Fl_Group::end();
  }

  const char *repcmd() {
    sprintf(cmdbuf, "VDW %f %f", radius->value(), resolution->value());
    return cmdbuf;
  }
  void set_values(AtomRep *rep) {
    radius->value(rep->get_data(AtomRep::SPHERERAD));
    resolution->value(rep->get_data(AtomRep::SPHERERES));
  }

protected:
  void do_reset() {
    radius->value(1.0);
    resolution->value(12);
  }

protected:
  Fl_Counter *radius;
  Fl_Counter *resolution;
};
 

/// 'Beads' representation controls
class GraphicsFltkRepBeads : public GraphicsFltkRepVDW {
public:
  GraphicsFltkRepBeads(Fl_Callback *cb, void *v)
  : GraphicsFltkRepVDW(cb, v) {}
  const char *repcmd() {
    sprintf(cmdbuf, "Beads %f %f", radius->value(), resolution->value()); 
    return cmdbuf;
  }
};


/// 'Dotted' representation controls
class GraphicsFltkRepDotted : public GraphicsFltkRepVDW {
public:
  GraphicsFltkRepDotted(Fl_Callback *cb, void *v)
  : GraphicsFltkRepVDW(cb, v) {}
  const char *repcmd() {
    sprintf(cmdbuf, "Dotted %f %f", radius->value(), resolution->value()); 
    return cmdbuf;
  }
};


/// 'Trace' representation controls
class GraphicsFltkRepTrace : public GraphicsFltkRepBonds {
public:
  GraphicsFltkRepTrace(Fl_Callback *cb, void *v)
  : GraphicsFltkRepBonds(cb, v) {}
  const char *repcmd() {
    sprintf(cmdbuf, "Trace %f %f", radius->value(), resolution->value());
    return cmdbuf;
  }
};


/// 'Licorice' representation controls
class GraphicsFltkRepLicorice : public GraphicsFltkRepBonds {
private:
  Fl_Counter *sresolution;
public:
  GraphicsFltkRepLicorice(Fl_Callback *cb, void *v)
  :  GraphicsFltkRepBonds(cb, v) {
    Fl_Group::begin();
    sresolution= new ResolutionCounter(x()+170,y()+60,"Sphere Resolution");
    sresolution->callback(cb, v);

    Fl_Group::end();
  }
protected:
  void do_reset() {
    GraphicsFltkRepBonds::do_reset();
    resolution->value(12);
    sresolution->value(12);
  }
public:
  const char *repcmd() {
    sprintf(cmdbuf, "Licorice %f %f %f", radius->value(), sresolution->value(),
      resolution->value());
    return cmdbuf;
  }
  void set_values(AtomRep *rep) {
    GraphicsFltkRepBonds::set_values(rep);
    sresolution->value(rep->get_data(AtomRep::SPHERERES));
  }
};
    

#ifdef VMDPOLYHEDRA
/// 'Polyhedra' representation controls
class GraphicsFltkRepPolyhedra : public GraphicsFltkRep {
private:
  Fl_Counter *distance;
public:
  GraphicsFltkRepPolyhedra(Fl_Callback *cb, void *v) {
    distance = new RadiusCounter(x()+170,y()+60,"Distance Cutoff");
    distance->callback(cb, v);
    Fl_Group::end();
  }
protected:
  void do_reset() {
    distance->value(1.6);
  }
public:
  const char *repcmd() {
    sprintf(cmdbuf, "Polyhedra %f", distance->value());
    return cmdbuf;
  }
  void set_values(AtomRep *rep) {
    distance->value(rep->get_data(AtomRep::SPHERERAD));
  }
};
#endif

/// 'Tube' representation controls
class GraphicsFltkRepTube : public GraphicsFltkRepBonds {
public:
  GraphicsFltkRepTube(Fl_Callback *cb, void *v)
  : GraphicsFltkRepBonds(cb, v) {
    radius->label("Radius");
    resolution->label("Resolution");
  }
  const char *repcmd() {
    sprintf(cmdbuf, "Tube %f %f", radius->value(), resolution->value());
    return cmdbuf;
  }
};
    

/// 'Ribbons' representation controls
class GraphicsFltkRepRibbons : public GraphicsFltkRepBonds {
protected:
  Fl_Counter *thickness;
public:
  GraphicsFltkRepRibbons(Fl_Callback *cb, void *v)
  : GraphicsFltkRepBonds(cb, v) {
    radius->label("Radius"); 
    resolution->label("Resolution");
    Fl_Group::begin();
    thickness = new ResolutionCounter(x()+170,y()+60,"Width");
    thickness->callback(cb, v);
    Fl_Group::end();
  }
protected:
  void do_reset() {
    GraphicsFltkRepBonds::do_reset();
    thickness->value(2);
  }
public:
  const char *repcmd() {
    sprintf(cmdbuf, "Ribbons %f %f %f", radius->value(), resolution->value(),
      thickness->value());
    return cmdbuf;
  }
  void set_values(AtomRep *rep) {
    GraphicsFltkRepBonds::set_values(rep);
    thickness->value(rep->get_data(AtomRep::LINETHICKNESS));
  }
};


/// 'NewRibbons' representation controls
class GraphicsFltkRepNewRibbons : public GraphicsFltkRep {
private:
  RepChoice *basis;
  IsoSlider *thickness, *ratio;
  ResolutionCounter *resolution;
public:
  GraphicsFltkRepNewRibbons(Fl_Callback *cb, void *v) {

    Fl_Group::begin();
    thickness = new IsoSlider(x()+80, y()+90, "Thickness");
    thickness->callback(cb, v);
    thickness->minimum(0.1);
    thickness->maximum(10.0);

    resolution= new ResolutionCounter(x()+170,y()+120,"Resolution");
    resolution->callback(cb, v);
    resolution->maximum(50.0);

    ratio= new IsoSlider(x()+80, y()+60, "Aspect Ratio");
    ratio->callback(cb, v);
    ratio->minimum(1.0);
    ratio->maximum(10.0);

    basis = new RepChoice(x()+170,y()+30,"Spline Style");
    basis->add("Catmull-Rom");
    basis->add("B-Spline");
    basis->callback(cb, v);
    Fl_Group::end();
  }
protected:
  void do_reset() {
    thickness->value(0.3);
    resolution->value(12);
    ratio->value(3);
    basis->value(0);
  }
public:
  const char *repcmd() {
    sprintf(cmdbuf, "NewRibbons %f %f %f %d", thickness->value(), 
        resolution->value(), ratio->value(), basis->value());
    return cmdbuf;
  }
  void set_values(AtomRep *rep) {
    ratio->value(rep->get_data(AtomRep::LINETHICKNESS));
    basis->value((int)rep->get_data(AtomRep::SPHERERAD));
    thickness->value(rep->get_data(AtomRep::BONDRAD));
    resolution->value(rep->get_data(AtomRep::BONDRES));
  }
};


/// 'Cartoon' representation controls
class GraphicsFltkRepCartoon : public GraphicsFltkRepRibbons {
public:
  GraphicsFltkRepCartoon(Fl_Callback *cb, void *v)
  : GraphicsFltkRepRibbons(cb, v) {
    radius->label("Helix/Coil Radius");
    resolution->label("Helix/Coil Resolution");
    thickness->label("Beta Sheet Thickness");
  }
  const char *repcmd() {
    sprintf(cmdbuf, "Cartoon %f %f %f", radius->value(), resolution->value(),
      thickness->value());
    return cmdbuf;
  }
protected:
  void do_reset() {
    radius->value(2.1);
    resolution->value(24.0);
    thickness->value(5.0);
  }
};


/// 'NewCartoon' representation controls
class GraphicsFltkRepNewCartoon : public GraphicsFltkRep {
private:
  RepChoice *basis;
  IsoSlider *thickness, *ratio;
  ResolutionCounter *resolution;
public:
  GraphicsFltkRepNewCartoon(Fl_Callback *cb, void *v) {

    Fl_Group::begin();
    thickness = new IsoSlider(x()+80, y()+90, "Thickness");
    thickness->callback(cb, v);
    thickness->minimum(0.1);
    thickness->maximum(10.0);

    resolution= new ResolutionCounter(x()+170,y()+120,"Resolution");
    resolution->callback(cb, v);
    resolution->maximum(50.0);

    ratio= new IsoSlider(x()+80, y()+60, "Aspect Ratio");
    ratio->callback(cb, v);
    ratio->minimum(1.0);
    ratio->maximum(10.0);

    basis = new RepChoice(x()+170,y()+30,"Spline Style");
    basis->add("Catmull-Rom");
    basis->add("B-Spline");
    basis->callback(cb, v);
    Fl_Group::end();
  }
protected:
  void do_reset() {
    thickness->value(0.3);
    resolution->value(10);
    ratio->value(4.1);
    basis->value(0);
  }
public:
  const char *repcmd() {
    sprintf(cmdbuf, "NewCartoon %f %f %f %d", thickness->value(), 
        resolution->value(), ratio->value(), basis->value());
    return cmdbuf;
  }
  void set_values(AtomRep *rep) {
    ratio->value(rep->get_data(AtomRep::LINETHICKNESS));
    basis->value((int)rep->get_data(AtomRep::SPHERERAD));
    thickness->value(rep->get_data(AtomRep::BONDRAD));
    resolution->value(rep->get_data(AtomRep::BONDRES));
  }
};

#ifdef VMDWITHCARBS

/// 
class GraphicsFltkRepWithRingSize : public GraphicsFltkRep {
protected:
  ResolutionCounter *max_ring_size;
  Fl_Callback *parentcb;
  void *parentdata;
  
  static void sync_cb(Fl_Widget *, void *v) {
    GraphicsFltkRepWithRingSize *self = (GraphicsFltkRepWithRingSize *)v;
    GraphicsFltkMenu *menu = (GraphicsFltkMenu *) self->parentdata;
    
    int max_ring_size = (int)self->max_ring_size->value();
    
    Molecule *m = menu->app->moleculeList->molecule(menu->molindex);
    
    for (int i=0; i<m->components(); i++) {
      DrawMolItem *d = m->component(i);
      AtomRep *ar = d->atomRep;

      if (ar->method() == AtomRep::RINGS_PAPERCHAIN || ar->method() == AtomRep::RINGS_TWISTER) {
        GraphicsFltkRepWithRingSize *rep;
        
        if (ar->method() == AtomRep::RINGS_PAPERCHAIN)
          rep = (GraphicsFltkRepWithRingSize *) menu->repcontrols.data("PaperChain");
        else
          rep = (GraphicsFltkRepWithRingSize *) menu->repcontrols.data("Twister");

        rep->set_values(ar);              
        rep->max_ring_size->value(max_ring_size);
        ar->change(rep->repcmd());
        d->force_recalc(d->MOL_REGEN);
      }        
    }

    // Don't call parent since we've already updated ourselves.
    // (self->parentcb)(self->max_ring_size, self->parentdata);
  }
};

/// 'PaperChain' representation controls
class GraphicsFltkRepPaperChain : public GraphicsFltkRepWithRingSize {
private:
  ShortSlider *bipyramid_height;
public:
  GraphicsFltkRepPaperChain(Fl_Callback *cb, void *v) {
    parentcb = cb;
    parentdata = v;

    Fl_Group::begin();

    bipyramid_height = new ShortSlider(x()+120, y()+30, "Bipyramid Height");
    bipyramid_height->callback(cb, v);
    bipyramid_height->minimum(0.1);
    bipyramid_height->maximum(10.0);

    max_ring_size = new ResolutionCounter(x()+150, y()+60, "Max. Ring Size");
    max_ring_size->callback(sync_cb,this);
    max_ring_size->minimum(2);
    max_ring_size->maximum(500);

    Fl_Group::end();
  }
protected:
  void do_reset() {
    bipyramid_height->value(1.0);
    max_ring_size->value(10);
  }
public:
  const char *repcmd() {
    sprintf(cmdbuf, "PaperChain %f %f", bipyramid_height->value(),
                                         max_ring_size->value());
    return cmdbuf;
  }
  void set_values(AtomRep *rep) {
    bipyramid_height->value(rep->get_data(AtomRep::LINETHICKNESS));
    max_ring_size->value((int)rep->get_data(AtomRep::ISOSTEPSIZE));
  }
};

/// 'Twister' representation controls
class GraphicsFltkRepTwister : public GraphicsFltkRepWithRingSize {
private:
  RepChoice *start_end_centroid;
  RepChoice *hide_shared_links;
  ResolutionCounter *rib_steps;
  ShortSlider *rib_width;
  ShortSlider *rib_height;
  ResolutionCounter *max_path_length;

public:
  GraphicsFltkRepTwister(Fl_Callback *cb, void *v) {
    parentcb = cb;
    parentdata = v;

    Fl_Group::begin();

    start_end_centroid = new RepChoice(x()+150, y()+30, "Start Ribbons At");
    start_end_centroid->callback(cb,v);
    start_end_centroid->add("Ring Edge");
    start_end_centroid->add("Ring Centroid");
    
    hide_shared_links = new RepChoice(x()+150, y()+60,"Hide Shared Links");
    hide_shared_links->callback(cb,v);
    hide_shared_links->add("No");
    hide_shared_links->add("Yes");
    
    rib_steps = new ResolutionCounter(x()+150, y()+90, "Steps in Ribbon");
    rib_steps->callback(cb,v);
    rib_steps->minimum(5);
    rib_steps->maximum(500);

    max_ring_size = new ResolutionCounter(x()+150, y()+120, "Max. Ring Size");
    max_ring_size->callback(sync_cb,this);
    max_ring_size->minimum(2);
    max_ring_size->maximum(500);

    max_path_length = new ResolutionCounter(x()+150, y()+150, "Max. Linking Distance");
    max_path_length->callback(cb,v);
    max_path_length->minimum(1);
    max_path_length->maximum(500);

    rib_width = new ShortSlider(x()+110, y()+180, "Ribbon Width");
    rib_width->callback(cb,v);
    rib_width->minimum(0.01);
    rib_width->maximum(10.0);

    rib_height = new ShortSlider(x()+110, y()+210, "Ribbon Height");
    rib_height->callback(cb,v);
    rib_height->minimum(0.01);
    rib_height->maximum(10.0);

    Fl_Group::end();
  }
protected:
  void do_reset() {
    start_end_centroid->value(1);
    hide_shared_links->value(0);
    rib_steps->value(10);
    rib_width->value(0.3);
    rib_height->value(0.05);
    max_ring_size->value(10);
    max_path_length->value(5);
  }
public:
  const char *repcmd() {
    sprintf(cmdbuf, "Twister %f %f %f %f %f %f %f", (float) start_end_centroid->value(),
                                                    (float) hide_shared_links->value(),
                                                    (float) rib_steps->value(),
                                                    rib_width->value(), rib_height->value(),
                                                    max_ring_size->value(), max_path_length->value());
    return cmdbuf;
  }
  void set_values(AtomRep *rep) {
    start_end_centroid->value((int)rep->get_data(AtomRep::LINETHICKNESS));
    hide_shared_links->value((int)rep->get_data(AtomRep::BONDRES));
    rib_steps->value((int)rep->get_data(AtomRep::SPHERERAD));
    rib_width->value(rep->get_data(AtomRep::BONDRAD));
    rib_height->value(rep->get_data(AtomRep::SPHERERES));
    max_ring_size->value((int)rep->get_data(AtomRep::ISOSTEPSIZE));
    max_path_length->value((int)rep->get_data(AtomRep::ISOLINETHICKNESS));
  }
};

#endif

/// 'Solvent' representation controls
class GraphicsFltkRepSolvent : public GraphicsFltkRep {
private:
  RepChoice *method;
  Fl_Counter *detail;
  Fl_Counter *probe; 
public:
  GraphicsFltkRepSolvent(Fl_Callback *cb, void *v) {
    method = new RepChoice(x()+170,y()+60,"Representation Method");
    method->add("Points");
    method->add("Crosses");
    method->add("Mesh");
    detail = new ResolutionCounter(x()+170,y()+90,"Detail Level");
    detail->maximum(13);
    probe = new RadiusCounter(x()+170,y()+120,"Probe Radius");
    method->callback(cb, v);
    detail->callback(cb, v);
    probe->callback(cb, v);
    Fl_Group::end();
  }
protected:
  void do_reset() {
    method->value(0);
    detail->value(7.0);
    probe->value(0);
  }
public:
  const char *repcmd() {
    sprintf(cmdbuf, "Solvent %f %f %f", probe->value(), detail->value(),
      (float)(method->value()+1));
    return cmdbuf;
  }
  void set_values(AtomRep *rep) {
    method->setvalue((int)rep->get_data(AtomRep::LINETHICKNESS)-1);
    detail->value(rep->get_data(AtomRep::SPHERERES));
    probe->value(rep->get_data(AtomRep::SPHERERAD));
  }
};
 

/// 'MSMS' representation controls
class GraphicsFltkRepMSMS : public GraphicsFltkRep {
private:
  Fl_Counter *density;
  Fl_Counter *probe;
  RepChoice *method;
  RepChoice *whichatoms;
public:
  GraphicsFltkRepMSMS(Fl_Callback *cb, void *v) {
    density = new RadiusCounter(x()+170,y()+90,"Sample Density");
    density->minimum(0.1);
    density->maximum(30.0);
    density->callback(cb, v);
    probe = new RadiusCounter(x()+170,y()+120, "Probe Radius");
    probe->minimum(0.1);
    probe->maximum(8.0);
    probe->callback(cb, v);
    method = new RepChoice(x()+170,y()+60,"Representation Method");
    method->add("Solid Surface");
    method->add("Wireframe");
    method->callback(cb, v);
    whichatoms = new RepChoice(x()+170,y()+30, "Which Atoms");
    whichatoms->add("Selected");
    whichatoms->add("All");
    whichatoms->callback(cb, v);
    Fl_Group::end();
  }
protected:
  void do_reset() {
    density->value(1.5);
    probe->value(1.5);
    method->value(0);
    whichatoms->value(0);
  }
public:
  const char *repcmd() {
    sprintf(cmdbuf, "MSMS %f %f %f %f", probe->value(), density->value(),
      (float)whichatoms->value(), (float)method->value());
    return cmdbuf;
  }
  void set_values(AtomRep *rep) {
    density->value(rep->get_data(AtomRep::SPHERERES));
    probe->value(rep->get_data(AtomRep::SPHERERAD));
    method->setvalue((int)rep->get_data(AtomRep::BONDRES));
    whichatoms->setvalue((int)rep->get_data(AtomRep::LINETHICKNESS));
  }
};
  

/// 'NanoShaper' representation controls
class GraphicsFltkRepNanoShaper : public GraphicsFltkRep {
private:
  RepChoice *surftype;
  RepChoice *method;
  Fl_Counter *gspacing;
  Fl_Counter *probe;
  Fl_Counter *skin;
  Fl_Counter *blob;

public:
  GraphicsFltkRepNanoShaper(Fl_Callback *cb, void *v) {
    surftype = new RepChoice(x()+170,y()+30, "Surface Type");
    surftype->add("SES");
    surftype->add("Skin");
    surftype->add("Blobby");
    surftype->callback(cb, v);
    method = new RepChoice(x()+170,y()+60,"Representation Method");
    method->add("Solid Surface");
    method->add("Wireframe");
    method->callback(cb, v);
    gspacing = new RadiusCounter(x()+170,y()+90,"Grid Spacing");
    gspacing->minimum(0.1);
    gspacing->maximum(30.0);
    gspacing->callback(cb, v);

    probe = new RadiusCounter(x()+170,y()+120, "SES Probe Radius");
    probe->minimum(0.1);
    probe->maximum(8.0);
    probe->callback(cb, v);

    skin = new RadiusCounter(x()+170,y()+150, "Skin Parameter");
    skin->minimum(0.01);
    skin->maximum(0.99);
    skin->callback(cb, v);

    blob = new RadiusCounter(x()+170,y()+180, "Blob Parameter");
    blob->minimum(-5.0);
    blob->maximum(-0.5);
    blob->callback(cb, v);

    Fl_Group::end();
  }
protected:
  void do_reset() {
    surftype->value(0); // SES
    method->value(0);
    gspacing->value(0.5);
    probe->value(1.4);
    probe->activate();
    skin->value(0.45);
    skin->deactivate();
    blob->value(-2.5);
    blob->deactivate();
  }
public:
  const char *repcmd() {
    sprintf(cmdbuf, "NanoShaper %f %f %f %f %f %f", 
      (float)surftype->value(), (float)method->value(), 
      gspacing->value(), probe->value(), skin->value(), blob->value());
    return cmdbuf;
  }

  void set_values(AtomRep *rep) {
    int sftype = rep->get_data(AtomRep::LINETHICKNESS);
    surftype->setvalue(sftype);
       
    method->setvalue((int)rep->get_data(AtomRep::BONDRES));
    gspacing->value(rep->get_data(AtomRep::GRIDSPACING));
    probe->value(rep->get_data(AtomRep::SPHERERAD));
    skin->value(rep->get_data(AtomRep::SPHERERES));
    blob->value(rep->get_data(AtomRep::BONDRAD));

    switch (sftype) {
     default: 
      case 0: // SES
        probe->activate();
        skin->deactivate();       
        blob->deactivate();
        break;

      case 1: // Skin
        probe->deactivate();
        skin->activate();       
        blob->deactivate();
        break;

      case 2: // Blob
        probe->deactivate();
        skin->deactivate();       
        blob->activate();
        break;
    }
  }
};
  

/// 'HBonds' representation controls
class GraphicsFltkRepHBonds : public GraphicsFltkRep {
private:
  Fl_Counter *distance;
  Fl_Counter *angle;
  Fl_Counter *thickness;
public:
  GraphicsFltkRepHBonds(Fl_Callback *cb, void *v) {
    distance = new RadiusCounter(x()+170,y()+60,"Distance Cutoff");
    distance->callback(cb, v);
    angle = new RadiusCounter(x()+170,y()+90,"Angle Cutoff");
    angle->precision(0);
    angle->step(1);
    angle->lstep(10);
    angle->callback(cb, v);
    thickness = new ResolutionCounter(x()+170,y()+120, "Line Thickness");
    thickness->callback(cb, v);
    Fl_Group::end();
  }
protected:
  void do_reset() {
    distance->value(3.0);
    angle->value(20.0);
    thickness->value(1.0);
  }
public:
  const char *repcmd() {
    sprintf(cmdbuf, "HBonds %f %f %f", distance->value(), angle->value(),
      thickness->value());
    return cmdbuf;
  }
  void set_values(AtomRep *rep) {
    distance->value(rep->get_data(AtomRep::BONDRAD));
    angle->value(rep->get_data(AtomRep::SPHERERAD));
    thickness->value(rep->get_data(AtomRep::LINETHICKNESS));
  }
};
     


//  QuickSurf callback data structure containing multiple pointers
class GraphicsFltkRepQuickSurf;    ///< forward declaration
/// QuickSurf callback data structure containing multiple pointers
class quicksurf_cbdata {
public:
  GraphicsFltkMenu *self;
  GraphicsFltkRepQuickSurf *qsurfrep;
};

/// 'QuickSurf' representation controls
class GraphicsFltkRepQuickSurf : public GraphicsFltkRep {
private:
  Fl_Value_Slider *resolution;
  RepChoice *quality;
  Fl_Counter *radscale;
  Fl_Counter *gridspacing;
  Fl_Counter *isovalue;
  float gridspacingmult;

public:
  GraphicsFltkRepQuickSurf(Fl_Callback *cb, void *v) {
    gridspacingmult = 1.0f;
    resolution = new IsoSlider(x()+80, y()+60, "Resolution");
    resolution->minimum(0.5);
    resolution->maximum(8);
    resolution->callback(cb, v);
    resolution->when(FL_WHEN_CHANGED | FL_WHEN_RELEASE_ALWAYS);
    radscale = new RadiusCounter(x()+170,y()+90,"Radius Scale");
    radscale->minimum(0.1);
    radscale->callback(cb, v);
    isovalue = new RadiusCounter(x()+170,y()+120,"Density Isovalue");
    isovalue->minimum(0.1);
    isovalue->maximum(20);
    isovalue->callback(cb, v);
    gridspacing = new RadiusCounter(x()+170,y()+150,"Grid Spacing");
    gridspacing->minimum(0.5);
    gridspacing->callback(cb, v);
    quality = new RepChoice(x()+170,y()+180, "Surface Quality");
    quality->add("Low");
    quality->add("Medium");
    quality->add("High");
    quality->add("Max");
    quality->callback(cb, v);
    Fl_Group::end();
  }

  void set_gridspacing_multiplier(float mult) {
    gridspacingmult = mult;
  }

  void resolution_changed() {
    float val = (float) resolution->value();
    float res = val;
    
    int qual = quality->value();
    switch (qual) {
      case 0: res *= 2.0f; break;

      case 1: res *= 1.0f; break;

      case 2:
      case 3:
      default: res /= 2.0f; break;
    }
    
    float density = 0.5f;
    if (val > 3.0f) {
      density = 30.0f*(val-3.0f)+6.0f*powf((val-3.0f),2.0f)+10.0f*powf(val-3.0f,0.5f)+23.0f;
    } else if (val > 1) {
      density = 254.83f * expf(-1.018f * val) - 
                5071.0f * powf(val, -0.0189f) + 4980.0f;
    } else {
      density = val;
    }
  
    radscale->value(val);
    isovalue->value(density);
    gridspacing->value(res * gridspacingmult);
  }

protected:
  void do_reset() {
    resolution->value(1.0);
    radscale->value(1.0);
    gridspacing->value(1.0);
    isovalue->value(0.5);
    quality->value(1);
  }
public:
  const char *repcmd() {
    sprintf(cmdbuf, "QuickSurf %f %f %f %f", 
            radscale->value(), isovalue->value(), gridspacing->value(),
           (float)quality->value());
    return cmdbuf;
  }
  void set_values(AtomRep *rep) {
    radscale->value(rep->get_data(AtomRep::SPHERERAD));
    isovalue->value(rep->get_data(AtomRep::BONDRAD));
    gridspacing->value(rep->get_data(AtomRep::GRIDSPACING));
    quality->setvalue((int)rep->get_data(AtomRep::BONDRES));
  }
};


/// 'Surf' representation controls
class GraphicsFltkRepSurf : public GraphicsFltkRep {
private:
  Fl_Counter *probe;
  RepChoice *method;
public:
  GraphicsFltkRepSurf(Fl_Callback *cb, void *v) {
    probe = new RadiusCounter(x()+170,y()+120,"Probe Radius");
    probe->minimum(0.1);
    probe->callback(cb, v);
    method = new RepChoice(x()+170,y()+90, "Representation Method");
    method->add("Solid Surface");
    method->add("Wireframe");
    method->callback(cb, v);
    Fl_Group::end();
  }
protected:
  void do_reset() {
    probe->value(1.4);
    method->value(0);
  }
public:
  const char *repcmd() {
    sprintf(cmdbuf, "Surf %f %f", probe->value(), (float)method->value());
    return cmdbuf;
  }
  void set_values(AtomRep *rep) {
    probe->value(rep->get_data(AtomRep::SPHERERAD));
    method->setvalue((int)rep->get_data(AtomRep::BONDRES));
  }
};
 

/// Base class for reps that use volumetric data
/// XXX We can't get the information we need from AtomRep to fill in the 
/// data set names or their min/max ranges.  Thus we have to add methods
/// for GraphicsFltkMenu to tell us what this information is.
class GraphicsFltkRepVolumetric: public GraphicsFltkRep {
protected:
  RepChoice *dataset;

  /// volumetric rep internal class for managing min/max values
  struct minmax {
    double minval, maxval;
    minmax(double themin=0, double themax=0) : minval(themin), maxval(themax) {}
    int operator==(const minmax &v) {
      return (minval == v.minval && maxval == v.maxval);
    }
  };
  ResizeArray<minmax> minmaxlist;

  GraphicsFltkRepVolumetric(Fl_Callback *cb, void *v) {
    dataset = new RepChoice(x()+170, y()+30, "Vol");
    dataset->callback(cb, v);
  }
  virtual void do_dataset_clear() {}
  virtual void do_dataset_append(const char *, double, double) {}

public:
  int is_volumetric() { return 1; }
  void dataset_clear() {
    dataset->clear();
    dataset->deactivate();
    minmaxlist.clear();
    do_dataset_clear();
  }
  void dataset_append(const char *nm, double newmin, double newmax) {
    // Fltk doesn't allow adding a menu item with the same name as
    // an existing item, so we use replace, which also avoids 
    // problems with the escape characters interpreted by add()
    int ind = dataset->add("foobar"); 
    char *datalabel = new char[strlen(nm)+32];
    sprintf(datalabel, "vol%d: %s", ind, nm);
    dataset->replace(ind, datalabel);
    delete[] datalabel;
    
    minmaxlist.append(minmax(newmin, newmax));
    dataset->value(minmaxlist.num()-1);
    do_dataset_append(nm, newmin, newmax);
    dataset->activate();
  }
};



//  isosurface callback data structure containing multiple pointers
class GraphicsFltkRepIsosurface; ///< forward declaration
/// isosurface callback data structure containing multiple pointers
class isosurface_cbdata {
public:
  GraphicsFltkMenu *self;
  GraphicsFltkRepIsosurface *isorep;
};
        
/// 'Isosurface' representation controls
class GraphicsFltkRepIsosurface : public GraphicsFltkRepVolumetric {
private:
  Fl_Slider *isovalue;
  Fl_Float_Input *isoinput;
  Fl_Float_Input *isomininput, *isomaxinput;
  Fl_Counter *resolution;
  Fl_Counter *thickness;
  RepChoice *method;
  RepChoice *boundary;
  Fl_Callback *parentcb;
  void *parentdata;
  int gridstepsize;

  static void datasetchanged_cb(Fl_Widget *w, void *v) {
    GraphicsFltkRepIsosurface *self = (GraphicsFltkRepIsosurface *)v;
    if (self->minmaxlist.num() > 0) {
      minmax &m = self->minmaxlist[self->dataset->value()];
      self->isovalue->range(m.minval, m.maxval);
    } else {
      self->isovalue->range(0, 1);
    }
    (self->parentcb)(w, self->parentdata);
  }

  static void inputcb(Fl_Widget *, void *v) {
    GraphicsFltkRepIsosurface *self = (GraphicsFltkRepIsosurface *)v;
    char *endptr = NULL;
    const char *strval = self->isoinput->value();
    double val = strtod(strval, &endptr);
    if (endptr != strval) {
      // valid conversion performed
      self->isovalue->value(val);
      (self->parentcb)(self->isovalue, self->parentdata);
    }
  }
  
  static void minmaxinputcb(Fl_Widget *, void *v) {
    GraphicsFltkRepIsosurface *self = (GraphicsFltkRepIsosurface *)v;
    // parse values in input fields
    char *endptr = NULL;
    const char *minstrval = self->isomininput->value();
    double minval = strtod(minstrval, &endptr);
    if (endptr == minstrval) return; // invalid
    const char *maxstrval = self->isomaxinput->value();
    double maxval = strtod(maxstrval, &endptr);
    if (endptr == maxstrval) return; // invalid

    // fields are valid; update cached minmax
    if (self->minmaxlist.num() > 0) {
      minmax &m = self->minmaxlist[self->dataset->value()];
      m.minval = minval;
      m.maxval = maxval;
    }

    // update the slider itself.  If we made the value change, trigger
    // appropriate callbacks.
    self->isovalue->range(minval, maxval);
    if (self->isovalue->value() < minval) {
      self->isovalue->value(minval);
      slidercb(NULL, v);
    } else if (self->isovalue->value() > maxval) {
      self->isovalue->value(maxval);
      slidercb(NULL, v);
    } else {
      self->setInputFromSlider();
    }
  }

  void setInputFromSlider() {
    char buf[128];
    sprintf(buf, "%8g", (float)isovalue->value()); ///< XXX use snprintf
    isoinput->value(buf);
    sprintf(buf, "%8g", (float)isovalue->minimum());
    isomininput->value(buf);
    sprintf(buf, "%8g", (float)isovalue->maximum());
    isomaxinput->value(buf);
  }

  static void slidercb(Fl_Widget *, void *v) {
    GraphicsFltkRepIsosurface *self = (GraphicsFltkRepIsosurface *)v;
    self->setInputFromSlider();
    (self->parentcb)(self->isovalue, self->parentdata);
  }

protected:
  void do_dataset_clear() { 
    isovalue->deactivate();
    isoinput->deactivate();
    boundary->deactivate();
    method->deactivate();
    resolution->deactivate();
    thickness->deactivate();
  }
  void do_dataset_append(const char *, double min, double max) { 
    isovalue->activate();
    isoinput->activate();
    boundary->activate();
    method->activate();
    resolution->activate();
    thickness->activate();
    isovalue->range(min,max);
    setInputFromSlider();
  }
  void do_reset() {
    set_grid_stepsize(1); ///< full resolution by default
    resolution->value(1); ///< full resolution by default
    thickness->value(1);  ///< 1 pixel by default
    method->value(2);     ///< use the points method by default
    boundary->value(2);   ///< draw box and isosurface by default
    dataset->value(0);    ///< First volume dataset by default
  }

public:
  GraphicsFltkRepIsosurface(Fl_Callback *cb, void *v)
  : GraphicsFltkRepVolumetric(datasetchanged_cb, this) {
    parentcb = cb;
    parentdata = v;

    resolution= new StepCounter(x()+35,y()+90, "Step");
    resolution->callback(cb, v);
    thickness= new StepCounter(x()+35,y()+120, "Size");
    thickness->callback(cb, v);
    method = new RepChoice(x()+170,y()+90,"Draw");
    method->add("Solid Surface");
    method->add("Wireframe");
    method->add("Points");
    method->add("Shaded Points");
    method->callback(cb, v);
    boundary = new RepChoice(x()+170, y()+120, "Show");
    boundary->add("Isosurface");
    boundary->add("Box");
    boundary->add("Box+Isosurface");
    boundary->callback(cb, v); 
    isovalue = new Fl_Slider(x()+135,y()+60, 155, CTRHEIGHT, "");
    isovalue->type(FL_HOR_SLIDER);
    isovalue->color(VMDMENU_SLIDER_BG);
    isovalue->when(FL_WHEN_CHANGED);
    isovalue->callback(slidercb, this);
    isoinput = new Fl_Float_Input(x()+60, y()+60, 70, CTRHEIGHT, "Isovalue");
    isoinput->when(FL_WHEN_ENTER_KEY);
    isoinput->selection_color(VMDMENU_VALUE_SEL);
    isoinput->callback(inputcb, this);
    isovalue->when(FL_WHEN_CHANGED | FL_WHEN_RELEASE_ALWAYS);

    isomininput = new Fl_Float_Input(x()+45, y()+30, 45, CTRHEIGHT, "Range");
    isomininput->when(FL_WHEN_ENTER_KEY);
    isomininput->selection_color(VMDMENU_VALUE_SEL);
    isomininput->callback(minmaxinputcb, this);
    isomaxinput = new Fl_Float_Input(x()+90, y()+30, 45, CTRHEIGHT);
    isomaxinput->when(FL_WHEN_ENTER_KEY);
    isomaxinput->selection_color(VMDMENU_VALUE_SEL);
    isomaxinput->callback(minmaxinputcb, this);

    Fl_Group::end();
  }
  const char *repcmd() {
    if (dataset->size() > 0) {
      sprintf(cmdbuf, "Isosurface %f %d %d %d %d %d", 
              (float) isovalue->value(), 
              (int) dataset->value(), 
              (int) boundary->value(), 
              (int) method->value(), 
              gridstepsize + (int) resolution->value() - 1,
              (int) thickness->value());
    } else {
      sprintf(cmdbuf, "Isosurface");
    }
    return cmdbuf;
  }
  void set_values(AtomRep *rep) {
    dataset->setvalue((int)rep->get_data(AtomRep::SPHERERES));
    // minmax needs to update whenever dataset is changed or 
    // the selected rep is switched
    if (minmaxlist.num() > 0) {
      minmax &m = minmaxlist[dataset->value()]; 
      isovalue->range(m.minval, m.maxval);
    } else {
      isovalue->range(0, 1);
    }
    isovalue->value(rep->get_data(AtomRep::SPHERERAD));
    method->setvalue((int)rep->get_data(AtomRep::BONDRES));
    boundary->setvalue((int)rep->get_data(AtomRep::LINETHICKNESS));
    resolution->value(rep->get_data(AtomRep::ISOSTEPSIZE) - gridstepsize + 1);
    thickness->value(rep->get_data(AtomRep::ISOLINETHICKNESS));
    setInputFromSlider();
  }
  void set_grid_stepsize(int step) {
    gridstepsize = step;
  }
};
 

/// 'VolumeSlice' representation controls
class GraphicsFltkRepVolumeSlice : public GraphicsFltkRepVolumetric {
private:
  Fl_Value_Slider *slice;
  RepChoice *axis;
  RepChoice *quality;  // texture filtering mode

protected:
  void do_dataset_clear() {
    slice->deactivate();
    axis->deactivate();
    quality->deactivate();
  }
  void do_dataset_append(const char *, double, double) {
    slice->activate();
    axis->activate();
    quality->activate();
  }
  void do_reset() {
    slice->value(0.5);
    axis->value(0);
    quality->value(2);
  }
public:
  GraphicsFltkRepVolumeSlice(Fl_Callback *cb, void *v)
  : GraphicsFltkRepVolumetric(cb, v) {
    slice = new IsoSlider(x()+80, y()+60, "Slice Offset");
    slice->callback(cb, v);
    axis = new RepChoice(x()+170, y()+90, "Slice Axis");
    axis->add("X");
    axis->add("Y");
    axis->add("Z");
    axis->callback(cb, v);
    quality = new RepChoice(x()+170,y()+120, "Render Quality");
    quality->add("Low");
    quality->add("Medium");
    quality->add("High");
    quality->callback(cb, v);
    Fl_Group::end();
  }
  const char *repcmd() {
    if (dataset->size() > 0) {
      sprintf(cmdbuf, "VolumeSlice %f %f %f %f", slice->value(),
        (float)dataset->value(), (float)axis->value(), 
        (float)quality->value()); 
    } else {
      sprintf(cmdbuf, "VolumeSlice");
    }
    return cmdbuf;
  }
  void set_values(AtomRep *rep) {
    dataset->setvalue((int)rep->get_data(AtomRep::SPHERERES));
    slice->value(rep->get_data(AtomRep::SPHERERAD));
    axis->setvalue((int)rep->get_data(AtomRep::LINETHICKNESS)); 
    quality->setvalue((int)rep->get_data(AtomRep::BONDRES));
  }
};



//  orbital callback data structure containing multiple pointers
class GraphicsFltkRepOrbital;    ///< forward declaration
/// orbital callback data structure containing multiple pointers
class orbital_cbdata {
public:
  GraphicsFltkMenu *self;
  GraphicsFltkRepOrbital *orbrep;
};


/// 'Orbital' representation controls
class GraphicsFltkRepOrbital : public GraphicsFltkRep {
protected:
  RepChoice      *wavefnctype;
  RepChoiceSmall *wavefncspin;
  RepChoiceSmall *wavefncexcitation;
  RepChoiceSmall *orbitalindex;

  /// volumetric rep internal class for managing min/max values
  struct minmax {
    double minval, maxval;
    minmax(double themin=0, double themax=0) : minval(themin), maxval(themax) {}
    int operator==(const minmax &v) {
      return (minval == v.minval && maxval == v.maxval);
    }
  };
  ResizeArray<minmax> minmaxlist;


public:
  int is_orbital() { return 1; }
  void dataset_clear() {
    orbitalindex->clear();
    orbitalindex->deactivate();
    minmaxlist.clear();
    do_dataset_clear();
  }
  void dataset_append(const char *nm, double newmin, double newmax) {
    // Fltk doesn't allow adding a menu item with the same name as
    // an existing item, so we use replace, which also avoids
    // problems with the escape characters interpreted by add()
    int ind = orbitalindex->add("foobar");
    char *datalabel = new char[strlen(nm)+32];
    sprintf(datalabel, "%s", nm);
    orbitalindex->replace(ind, datalabel);
    delete[] datalabel;

    minmaxlist.append(minmax(newmin, newmax));
    orbitalindex->value(minmaxlist.num()-1);
    do_dataset_append(nm, newmin, newmax);
    orbitalindex->activate();
  }

  void regen_wavefunctypes() {
    int i;
    orbital_cbdata *cbdata = (orbital_cbdata *) parentdata;
    GraphicsFltkMenu *menu = (GraphicsFltkMenu *) cbdata->self;
    Molecule *m = menu->app->moleculeList->molecule(menu->molindex);
    int firstvalidwaveftype=-1;
    int firstvalidwavefspin=-1;

    if (m != NULL && m->qm_data != NULL) {
      // loop over wavefunction types and enable/disable them
      // based on their availability for the current molecule.
      for (i=0; i<7; i++) {
        if (m->qm_data->has_wavef_guitype(i)) {
          wavefnctype->mode(i, 0);                 // activate choice
          if (firstvalidwaveftype < 0)
            firstvalidwaveftype=i;                 // record first valid type
        } else {
          wavefnctype->mode(i, FL_MENU_INACTIVE);  // deactivate choice
        }
      }

      // loop over wavefunction spins and enable/disable them
      // based on their availability for the current molecule.
      for (i=0; i<2; i++) {
        if (m->qm_data->has_wavef_spin(i)) {
          wavefncspin->mode(i, 0);
          if (firstvalidwavefspin < 0)
            firstvalidwavefspin=i;                 // record first valid spin
        } else {
          wavefncspin->mode(i, FL_MENU_INACTIVE);
        }
      }
    } else {
      // no qm data or no molecule, so all types/spins disabled
      for (i=0; i<7; i++)
        wavefnctype->mode(i, FL_MENU_INACTIVE);
      for (i=0; i<2; i++)
        wavefncspin->mode(i, FL_MENU_INACTIVE);
    }

    // automatically set the wavefunction type control to the first valid type
    if (firstvalidwaveftype >= 0)
      wavefnctype->value(firstvalidwaveftype);

    // automatically set the wavefunction spin control to the first valid spin
    if (firstvalidwavefspin >= 0)
      wavefncspin->value(firstvalidwavefspin);

    // regenerate the excitation chooser as well
    regen_excitationlist();
  }

  void regen_excitationlist() {
    orbital_cbdata *cbdata = (orbital_cbdata *) parentdata;
    GraphicsFltkMenu *menu = (GraphicsFltkMenu *) cbdata->self;
    Molecule *m = menu->app->moleculeList->molecule(menu->molindex);

    int wftype = (int) wavefnctype->value();
    if (m != NULL && m->qm_data != NULL && 
        m->qm_data->has_wavef_guitype(wftype)) {
      int maxexcitations = m->qm_data->get_highest_excitation(wftype);
      wavefncexcitation->clear();
      wavefncexcitation->deactivate();

      // regenerate the contents of the excitation chooser based
      // on the currently selected wavefunction type
      int i;
      for (i=0; i<=maxexcitations; i++) {
        char excitationstr[64];
        sprintf(excitationstr, "%d", i);
        wavefncexcitation->add(excitationstr);
      }

      // toggle excitations active/inactive in the GUI
      int firstvalidexcitation=-1;
      for (i=0; i<=maxexcitations; i++) {
        if (m->qm_data->find_wavef_id_from_gui_specs(wftype, GUI_WAVEF_SPIN_ALPHA, i)>=0 ||
            m->qm_data->find_wavef_id_from_gui_specs(wftype, GUI_WAVEF_SPIN_BETA, i)>=0) {
          // record the first valid excitation for this wavefunction type
          if (firstvalidexcitation < 0)
            firstvalidexcitation=i;
          wavefncexcitation->mode(i, 0); // enable in the GUI
        } else {
          wavefncexcitation->mode(i, FL_MENU_INACTIVE); // disable in the GUI
        }
      }

      // automatically set the excitation control to the first valid
      // excitation we find (if any)
      if (firstvalidexcitation >= 0)
        wavefncexcitation->value(firstvalidexcitation);
      wavefncexcitation->activate();
    }
  }

  void regen_orbitallist(int orblistcenter=10) {
    dataset_clear();
    orbital_cbdata *cbdata = (orbital_cbdata *) parentdata;
    GraphicsFltkMenu *menu = (GraphicsFltkMenu *) cbdata->self;
    Molecule *m = menu->app->moleculeList->molecule(menu->molindex);
    if (m != NULL && m->qm_data != NULL) {
      char namestr[80];

      int waveid = 0;
      int wftype = (int) wavefnctype->value();
      int wfspin = (int) wavefncspin->value();
      int wfexcitation = (int) wavefncexcitation->value();

      // convert GUI selections of wavefunction type, spin, and excitation
      // into the correct internal wavefunction ID tag
      waveid = m->qm_data->find_wavef_id_from_gui_specs(wftype, wfspin, wfexcitation);

      if (waveid < 0) {
        orbitalindex->activate();
        return;
      }

      // get 1-based maximum orbital index 
      int molmaxorb = m->qm_data->get_max_avail_orbitals(waveid);

      // set maximum orbital list center value for selected wavefunction
      // (1-based index)
      orbitallistcounter->maximum(molmaxorb);

      // check for orblistcenter for -1, and read from orbitallistcounter
      if (orblistcenter == -1)
        orblistcenter = (int) orbitallistcounter->value();
      else 
        orbitallistcounter->value(orblistcenter);

      // clamp orblistcenter close to valid range
      if (orblistcenter > (molmaxorb-10))
        orblistcenter = molmaxorb-10;
      if (orblistcenter < 10)
        orblistcenter = 10;

      int maxorb = orblistcenter+10;
      int minorb = orblistcenter-10;

      // clamp range of orbital list precisely
      if (minorb < 1) 
        minorb=1;
      if (maxorb > molmaxorb) 
        maxorb=molmaxorb;

      // set class minimum chooser index info
      orbitalminindex = minorb - 1; // use 0-based indexing here

#if 0
      // XXX not ready for prime time yet.
      // query the HOMO for the zeroth timestep and use it when listing
      // the orbitals, as the current implementation doesn't sort the
      // orbitals differently for different timesteps, so the HOMO index
      // for timestep zero will also be correct for the others.
      int HOMO = -1; // init with sentinel so it's only listed if we've got it
      Timestep *ts = m->get_frame(0); // timestep zero
      if (ts != NULL && ts->qm_timestep != NULL &&
          ts->qm_timestep->get_num_wavef()) {
        int tmp = ts->qm_timestep->get_homo(waveid);
        HOMO = ts->qm_timestep->get_orbital_id_from_index(waveid, tmp);
printf("HOMO: %d\n", HOMO);
      } else {
printf("HOMO not set: %d\n", HOMO);
      }
#endif

      // regenerate list of orbitals (1-based)
      // returned GUI indices are 0-based
      int j;
      for (j=minorb; j<=maxorb; j++) {
#if 1
        sprintf(namestr, "%3d", j);
#else
        if (HOMO == j)
          sprintf(namestr, "%3d (HOMO)", j); // tag HOMO specially
        else
          sprintf(namestr, "%3d", j); // others currently just list their ID
#endif

        dataset_append(namestr, -0.1, 0.1);
      }
      for (j=minorb; j<=maxorb; j++) {
        // check whether this orbital exists or not
        // GUI indices are 0-based
        if (m->qm_data->has_orbital(waveid, j))
          orbitalindex->mode(j-1, 0); // enable in the GUI
        else 
          orbitalindex->mode(j-1, FL_MENU_INACTIVE); // disable in the GUI
      }

      orbitalindex->value(0);
      orbitalindex->activate();
    } else {
      orbitalindex->value(0);
      orbitalindex->activate();
    }
  }


private:
  Fl_Slider *isovalue;
  Fl_Float_Input *isoinput;
  Fl_Float_Input *isomininput, *isomaxinput;
  Fl_Counter *resolution;
  Fl_Counter *thickness;
  RepChoice *method;
  Fl_Callback *parentcb;
  void *parentdata;
  RepChoice *boundary;
  int gridstepsize;
  Fl_Counter *orbitallistcounter;
  int orbitalminindex;

  static void datasetchanged_cb(Fl_Widget *w, void *v) {
    GraphicsFltkRepOrbital *self = (GraphicsFltkRepOrbital *)v;
    if (self->minmaxlist.num() > 0) {
      minmax &m = self->minmaxlist[self->orbitalindex->value()];
      self->isovalue->range(m.minval, m.maxval);
    } else {
      self->isovalue->range(0, 1);
    }
    (self->parentcb)(w, self->parentdata);
  }

  static void inputcb(Fl_Widget *, void *v) {
    GraphicsFltkRepOrbital *self = (GraphicsFltkRepOrbital *)v;
    char *endptr = NULL;
    const char *strval = self->isoinput->value();
    double val = strtod(strval, &endptr);
    if (endptr != strval) {
      // valid conversion performed
      self->isovalue->value(val);
      (self->parentcb)(self->isovalue, self->parentdata);
    }
  }
  
  static void minmaxinputcb(Fl_Widget *, void *v) {
    GraphicsFltkRepOrbital *self = (GraphicsFltkRepOrbital *)v;
    // parse values in input fields
    char *endptr = NULL;
    const char *minstrval = self->isomininput->value();
    double minval = strtod(minstrval, &endptr);
    if (endptr == minstrval) return; // invalid
    const char *maxstrval = self->isomaxinput->value();
    double maxval = strtod(maxstrval, &endptr);
    if (endptr == maxstrval) return; // invalid

    // fields are valid; update cached minmax
    if (self->minmaxlist.num() > 0) {
      minmax &m = self->minmaxlist[self->orbitalindex->value()];
      m.minval = minval;
      m.maxval = maxval;
    }

    // update the slider itself.  If we made the value change, trigger
    // appropriate callbacks.
    self->isovalue->range(minval, maxval);
    if (self->isovalue->value() < minval) {
      self->isovalue->value(minval);
      slidercb(NULL, v);
    } else if (self->isovalue->value() > maxval) {
      self->isovalue->value(maxval);
      slidercb(NULL, v);
    } else {
      self->setInputFromSlider();
    }
  }

  void setInputFromSlider() {
    char buf[128];
    sprintf(buf, "%8g", (float)isovalue->value()); ///< XXX use snprintf
    isoinput->value(buf);
    sprintf(buf, "%8g", (float)isovalue->minimum());
    isomininput->value(buf);
    sprintf(buf, "%8g", (float)isovalue->maximum());
    isomaxinput->value(buf);
  }

  static void slidercb(Fl_Widget *, void *v) {
    GraphicsFltkRepOrbital *self = (GraphicsFltkRepOrbital *)v;
    self->setInputFromSlider();
    (self->parentcb)(self->isovalue, self->parentdata);
  }

protected:
  void do_dataset_clear() { 
    isovalue->deactivate();
    isoinput->deactivate();
    boundary->deactivate();
    method->deactivate();
    resolution->deactivate();
    thickness->deactivate();
    orbitallistcounter->deactivate();
  }
  void do_dataset_append(const char *, double min, double max) { 
    isovalue->activate();
    isoinput->activate();
    boundary->activate();
    method->activate();
    resolution->activate();
    thickness->activate();
    orbitallistcounter->activate();
    isovalue->range(min,max);
    setInputFromSlider();
  }
  void do_reset() {
    set_grid_stepsize(1);           ///< full resolution by default
    resolution->value(0.075);       ///< default resolution
    thickness->value(1);            ///< 1 pixel by default
    orbitallistcounter->minimum(1); ///< minimum orbital value is 1
    orbitallistcounter->value(10);  ///< center selector on orb 10 initially
    orbitallistcounter->step(10);
    orbitallistcounter->lstep(50);
    method->value(0);               ///< use the solid surface method by default
    boundary->value(0);             ///< draw box and isosurface by default
    wavefnctype->value(0);          ///< Default wavefunction type
    wavefncspin->value(0);          ///< Default spin
    wavefncexcitation->value(0);    ///< Default excitation
    orbitalindex->value(0);         ///< First orbital by default
    isovalue->value(0.05);          ///< initially set to isovalue of 0.05

    // regenerate GUI selectors
    regen_wavefunctypes();
    regen_orbitallist();
  }

public:
  GraphicsFltkRepOrbital(Fl_Callback *cb, void *v) {
    parentcb = cb;
    parentdata = v;
    gridstepsize = 1;
    orbitalminindex = 0;

    int gx = x();
    int gy = y();

    // The order of wavefunction type indexes is hard-coded and cannot 
    // be changed safely without introducing extra mapping logic
    wavefnctype = new RepChoice(gx+170, gy+30, "Wavefunction Type");
    wavefnctype->add("Canonical");
    wavefnctype->add("GVB geminal pairs");
    wavefnctype->add("MCSCF natural");
    wavefnctype->add("MCSCF optimized");
    wavefnctype->add("CI natural");
    wavefnctype->add("Localized");
    wavefnctype->add("Other");
    wavefnctype->callback(cb, v);

    wavefncspin = new RepChoiceSmall(gx+65, gy+60, "Spin");
    wavefncspin->add("Alpha");
    wavefncspin->add("Beta");
    wavefncspin->callback(cb, v);

    wavefncexcitation = new RepChoiceSmall(gx+65, gy+90, "Excitation");
    int i;
    for (i=0; i<11; i++) {
      char excitationstr[64];
      sprintf(excitationstr, "%d", i);
      wavefncexcitation->add(excitationstr);
    }
    wavefncexcitation->callback(cb, v);

    gy+=60;

    orbitallistcounter= new StepCounter(gx+200,gy, "OrbList");
    orbitallistcounter->callback(cb, v);
    orbitalindex = new RepChoiceSmall(gx+210, gy+30, "Orbital");
    orbitalindex->callback(cb, v);

    resolution= new GridResCounter(gx+170,gy+150, "Grid Spacing");
    resolution->callback(cb, v);
    thickness= new StepCounter(gx+40,gy+120, "Size");
    thickness->callback(cb, v);
    method = new RepChoice(gx+170,gy+90,"Draw");
    method->add("Solid Surface");
    method->add("Wireframe");
    method->add("Points");
    method->add("Shaded Points");
    method->callback(cb, v);
    boundary = new RepChoice(gx+170, gy+120, "Show");
    boundary->add("Isosurface");
    boundary->add("Box");
    boundary->add("Box+Isosurface");
    boundary->callback(cb, v); 
    isovalue = new Fl_Slider(gx+150,gy+60, 140, CTRHEIGHT, "");
    isovalue->type(FL_HOR_SLIDER);
    isovalue->color(VMDMENU_SLIDER_BG);
    isovalue->when(FL_WHEN_CHANGED);
    isovalue->callback(slidercb, this); 
    isoinput = new Fl_Float_Input(gx+80, gy+60, 70, CTRHEIGHT, "Isovalue");
    isoinput->when(FL_WHEN_ENTER_KEY);
    isoinput->selection_color(VMDMENU_SLIDER_SEL);
    isoinput->callback(inputcb, this);
    isovalue->when(FL_WHEN_CHANGED | FL_WHEN_RELEASE_ALWAYS);

    isomininput = new Fl_Float_Input(gx+40, gy+90, 45, CTRHEIGHT, "Range");
    isomininput->when(FL_WHEN_ENTER_KEY);
    isomininput->selection_color(VMDMENU_VALUE_SEL);
    isomininput->callback(minmaxinputcb, this);
    isomaxinput = new Fl_Float_Input(gx+85, gy+90, 45, CTRHEIGHT);
    isomaxinput->when(FL_WHEN_ENTER_KEY);
    isomaxinput->selection_color(VMDMENU_VALUE_SEL);
    isomaxinput->callback(minmaxinputcb, this);

    Fl_Group::end();
  }
  const char *repcmd() {
    sprintf(cmdbuf, "Orbital %f %d %d %d %.3f %d %d %d %d %d", 
            (float) isovalue->value(), 
            (int) (orbitalindex->value() + orbitalminindex + 1), // 1-based
            (int) boundary->value(), 
            (int) method->value(), 
            (float) resolution->value(),
            (int) thickness->value(),
            (int) wavefnctype->value(), 
            (int) wavefncspin->value(), 
            (int) wavefncexcitation->value(),
            gridstepsize);
    return cmdbuf;
  }
  void set_values(AtomRep *rep) {
    int oldtype = wavefnctype->value();
    int oldspin = wavefncspin->value();
    int oldexcitation = wavefncexcitation->value();

    int newtype = (int) rep->get_data(AtomRep::WAVEFNCTYPE);
    int newspin = (int) rep->get_data(AtomRep::WAVEFNCSPIN);
    int newexcitation = (int) rep->get_data(AtomRep::WAVEFNCEXCITATION);
  
    // Only regenerate the orbital list when required, otherwise
    // the updates interfere with the isovalue slider operation.
    if (oldtype != newtype || oldspin != newspin || 
        oldexcitation != newexcitation) {
      wavefnctype->setvalue(newtype);
      wavefncspin->setvalue(newspin);

      if (oldtype != newtype) {
        // regenerate the excitation chooser contents based on the
        // currently selected wavefunction type
        regen_excitationlist();
      } 
      wavefncexcitation->setvalue(newexcitation);
    }

    // regenerate the orbital index list if the wavefunction has
    // changed, or if a text command has gone beyond the range of
    // the existing orbital list.
    int oldorbidx = orbitalindex->value() + orbitalminindex + 1; // 1-based
    int neworbidx = (int)rep->get_data(AtomRep::SPHERERES);
    if (oldtype != newtype || oldspin != newspin || 
        oldexcitation != newexcitation ||
        abs(neworbidx-oldorbidx) > 10) {
      // regenerate the orbital chooser contents based on the
      // currently selected wavefunction type, spin, and excitation
      regen_orbitallist(neworbidx);
    }
    orbitalindex->setvalue(neworbidx - orbitalminindex - 1); // 0-based

    // minmax needs to update whenever dataset is changed or 
    // the selected rep is switched
    if (minmaxlist.num() > 0) {
      minmax &m = minmaxlist[orbitalindex->value()]; 
      isovalue->range(m.minval, m.maxval);
    } else {
      isovalue->range(0, 1);
    }
    isovalue->value(rep->get_data(AtomRep::SPHERERAD));
    method->setvalue((int)rep->get_data(AtomRep::BONDRES));
    boundary->setvalue((int)rep->get_data(AtomRep::LINETHICKNESS));
    resolution->value(rep->get_data(AtomRep::GRIDSPACING));
    thickness->value(rep->get_data(AtomRep::ISOLINETHICKNESS));
    set_grid_stepsize((int) rep->get_data(AtomRep::ISOSTEPSIZE));
    setInputFromSlider();
  }
  void set_grid_stepsize(int step) {
    if (step < 1)
      step=1;

    gridstepsize = step;
  }
};

 


/// 'FieldLines' representation controls
class GraphicsFltkRepFieldLines : public GraphicsFltkRepVolumetric {
private:
  Fl_Counter *thickness;
  Fl_Counter *delta;
  Fl_Value_Slider *gradmag;
  Fl_Value_Slider *minlen;
  Fl_Value_Slider *maxlen;
  RepChoiceSmall *drawstyle;
  RepChoiceSmall *seedmethod;

protected:
  void do_dataset_clear() {
    gradmag->deactivate();
    minlen->deactivate();
    maxlen->deactivate();
    thickness->deactivate();
    delta->deactivate();
    drawstyle->activate();
    seedmethod->activate();
  }
  void do_dataset_append(const char *, double, double) {
    gradmag->activate();
    minlen->activate();
    maxlen->activate();
    thickness->activate();
    delta->activate();
    drawstyle->activate();
    seedmethod->activate();
  }
  void do_reset() {
    gradmag->value(1.8);
    minlen->value(10);
    maxlen->value(50);
    thickness->value(1);
    delta->value(0.25);
    drawstyle->value(0);
    seedmethod->value(0);
  }

public:
  GraphicsFltkRepFieldLines(Fl_Callback *cb, void *v)
  : GraphicsFltkRepVolumetric(cb, v) {

    drawstyle = new RepChoiceSmall(x()+40,y()+60,"Style");
    drawstyle->add("Line");
    drawstyle->add("Tube");
    drawstyle->add("Spheres");
    drawstyle->callback(cb, v);

    seedmethod = new RepChoiceSmall(x()+40,y()+90,"Seed");
    seedmethod->add("Gradient");
    seedmethod->add("Grid");
    seedmethod->callback(cb, v);

    thickness = new ResolutionCounter(x()+170, y()+60, "Size");
    thickness->callback(cb, v);

    delta = new DeltaCounter(x()+170, y()+90, "Delta");
    delta->callback(cb, v);

    gradmag = new IsoSlider(x()+80,  y()+120, "GradientMag");
    gradmag->callback(cb, v);
    minlen = new IsoSlider(x()+80,  y()+150, "Min Length");
    minlen->callback(cb, v);
    maxlen = new IsoSlider(x()+80, y()+180, "Max Length");
    maxlen->callback(cb, v);

    Fl_Group::end();
  }
  const char *repcmd() {
    if (dataset->size() > 0) {
      sprintf(cmdbuf, "FieldLines %f %f %f %f %f %f %f %f", 
              (float)dataset->value(), gradmag->value(), minlen->value(), 
              maxlen->value(), thickness->value(), 
              (float)drawstyle->value(), (float)seedmethod->value(),
              delta->value());
    } else {
      sprintf(cmdbuf, "FieldLines");
    }
    return cmdbuf;
  }
  void set_values(AtomRep *rep) {
    dataset->setvalue((int)rep->get_data(AtomRep::SPHERERES));
    gradmag->range(0.05, 25.0);
    gradmag->value(rep->get_data(AtomRep::SPHERERAD));
    minlen->range(1, 500.0);
    minlen->value(rep->get_data(AtomRep::BONDRAD));
    maxlen->range(1, 500.0);
    maxlen->value(rep->get_data(AtomRep::BONDRES));
    thickness->range(1, 10);
    thickness->value(rep->get_data(AtomRep::LINETHICKNESS));
    delta->range(0.005, 4.0);
    delta->value(rep->get_data(AtomRep::FIELDLINEDELTA));
    drawstyle->setvalue((int) rep->get_data(AtomRep::FIELDLINESTYLE));
    seedmethod->setvalue((int) rep->get_data(AtomRep::FIELDLINESEEDUSEGRID));
  }
};


